/*=====================================================
Critical Links
Auto Configuration Server

Authors:
	Rui Eduardo Gouveia Gil - r-gil@critical-links.com

Date: 2010
Version 0.1
=======================================================*/
#include "httpd/HttpFileService.h"
#include "httpd/HttpUserManager.h"
#include "ACSConfig.h"

namespace acs
{


HttpFileService::HttpFileService() : cacheSetting(DEFAULT_CACHE_SETTING), scanSetting(DEFAULT_SCAN_SETTING), maxCacheSize(DEFAULT_MAX_CACHE_SIZE), maxChunkSize(DEFAULT_MAX_CHUNK_SIZE), writable(false)
{
	this->setOption("directory",ACSConfig::getWebDirectory());
	this->setOption("file",ACSConfig::getWebDirectory()+"index.html");
}

HttpFileService::~HttpFileService()
{

}

void HttpFileService::setOption(const std::string& name, const std::string& value)
{
	if (name == "directory")
	{
		directory = value;
		pion::PionPlugin::checkCygwinPath(directory, value);

		if (! boost::filesystem::exists(directory) )
		{
			Throw(rgcpp::DirectoryNotFoundException(value));
		}

		if (! boost::filesystem::is_directory(directory) )
		{
			Throw(rgcpp::DirectoryNotFoundException(std::string("Is not a directory: ")+value));
		}
	}
	else if (name == "file")
	{
		file = value;
		pion::PionPlugin::checkCygwinPath(file, value);

		if (! boost::filesystem::exists(file) )
		{
			Throw(rgcpp::FileNotFoundException(value));
		}

		if (boost::filesystem::is_directory(file) )
		{
			Throw(rgcpp::FileNotFoundException(std::string("Is not a file: ")+value));
		}
	}
	else if (name == "cache")
	{
		if (value == "0")
			cacheSetting = 0;
		else if (value == "1")
			cacheSetting = 1;
		else if (value == "2")
			cacheSetting = 2;
		else
			Throw(rgcpp::ConfigException(std::string("Invalid Option Value: ")+name+"="+value));
	}
	else if (name == "scan")
	{
		if (value == "0")
			scanSetting = 0;
		else if (value == "1")
			scanSetting = 1;
		else if (value == "2")
			scanSetting = 2;
		else if (value == "3")
			scanSetting = 3;
		else
			Throw(rgcpp::ConfigException(std::string("Invalid Option Value: ")+name+"="+value));
	}
	else if (name == "max_chunk_size")
	{
		maxChunkSize = boost::lexical_cast<unsigned long>(value);
	}
	else if (name == "writable")
	{
		if (value == "true")
			writable = true;
		else if (value == "false")
			writable = false;
		else
			Throw(rgcpp::ConfigException(std::string("Invalid Option Value: ")+name+"="+value));
	}
	else
	{
		Throw(rgcpp::ConfigException(std::string("Invalid Option: ")+name));
	}
}

void HttpFileService::operator()(pion::net::HTTPRequestPtr& request, pion::net::TCPConnectionPtr& tcpConn)
{
	HttpUserThreadBind httpUserThreadBind(request->getUser());

	const std::string relativePath(getRelativeResource(request->getResource()));

	boost::filesystem::path filePath;
	if (relativePath.empty())
	{

		if (file.empty())
		{
			LOG_WARN_DATA("No file option defined (" << getResource() << ")");
			sendNotFoundResponse(request, tcpConn);
			return;
		}
		else
		{
			filePath = file;
		}
	}
	else
	{
		if (directory.empty())
		{
			LOG_WARN_DATA("No directory option defined (" << getResource() << "): " << relativePath);
			sendNotFoundResponse(request, tcpConn);
			return;
		}
		else
		{
			filePath = directory / relativePath;
		}
	}

	// make sure that the requested file is within the configured directory
	filePath.normalize();
	std::string file_string = filePath.file_string();
	if (file_string.find(directory.directory_string()) != 0)
	{
		LOG_WARN_DATA("Request for file outside of directory (" << getResource() << "): " << relativePath);

		static const std::string FORBIDDEN_HTML_START =	"<html><head>\n"
														"<title>403 Forbidden</title>\n"
														"</head><body>\n"
														"<h1>Forbidden</h1>\n"
														"<p>The requested URL ";

		static const std::string FORBIDDEN_HTML_FINISH =	" is not in the configured directory.</p>\n"
															"</body></html>\n";

		pion::net::HTTPResponseWriterPtr writer(pion::net::HTTPResponseWriter::create(tcpConn, *request, boost::bind(&pion::net::TCPConnection::finish, tcpConn)));
		writer->getResponse().setStatusCode(pion::net::HTTPTypes::HTTPTypes::RESPONSE_CODE_FORBIDDEN);
		writer->getResponse().setStatusMessage(pion::net::HTTPTypes::HTTPTypes::RESPONSE_MESSAGE_FORBIDDEN);

		if (request->getMethod() != pion::net::HTTPTypes::REQUEST_METHOD_HEAD)
		{
			writer->writeNoCopy(FORBIDDEN_HTML_START);
			writer << request->getResource();
			writer->writeNoCopy(FORBIDDEN_HTML_FINISH);
		}

		writer->send();
		return;
	}

	// requests specifying directories are not allowed
	if (boost::filesystem::is_directory(filePath))
	{
		LOG_WARN_DATA("Request for directory (" << getResource() << "): " << relativePath);

		static const std::string FORBIDDEN_HTML_START =	"<html><head>\n"
														"<title>403 Forbidden</title>\n"
														"</head><body>\n"
														"<h1>Forbidden</h1>\n"
														"<p>The requested URL ";

		static const std::string FORBIDDEN_HTML_FINISH =	" is a directory.</p>\n"
															"</body></html>\n";

		pion::net::HTTPResponseWriterPtr writer(pion::net::HTTPResponseWriter::create(tcpConn, *request, boost::bind(&pion::net::TCPConnection::finish, tcpConn)));
		writer->getResponse().setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_FORBIDDEN);
		writer->getResponse().setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_FORBIDDEN);

		if (request->getMethod() != pion::net::HTTPTypes::REQUEST_METHOD_HEAD)
		{
			writer->writeNoCopy(FORBIDDEN_HTML_START);
			writer << request->getResource();
			writer->writeNoCopy(FORBIDDEN_HTML_FINISH);
		}

		writer->send();
		return;
	}

	if (request->getMethod() == pion::net::HTTPTypes::REQUEST_METHOD_GET || request->getMethod() == pion::net::HTTPTypes::REQUEST_METHOD_HEAD)
	{
		DiskFile responseFile;
		ResponseType responseType = RESPONSE_UNDEFINED;
		const std::string ifModifiedSince(request->getHeader(pion::net::HTTPTypes::HEADER_IF_MODIFIED_SINCE));

		// check the cache for a corresponding entry (if enabled)
		// note that cacheSetting may equal 0 if scanSetting == 1
		if (cacheSetting > 0 || scanSetting > 0)
		{
			// search for a matching cache entry
			boost::mutex::scoped_lock cacheLock(cacheMutex);
			rgcpp::Map<std::string,DiskFile>::iterator cacheItr = cacheMap.find(relativePath);

			if (cacheItr == cacheMap.end())
			{
				if (scanSetting == 1 || scanSetting == 3)
				{
					// do not allow files to be added;
					// all requests must correspond with existing cache entries
					// since no match was found, just return file not found
					LOG_WARN_DATA("Request for unknown file ("  << getResource() << "): " << relativePath);
					responseType = RESPONSE_NOT_FOUND;
				}
				else
				{
					LOG_DEBUG_DATA("No cache entry for request (" << getResource() << "): " << relativePath);
				}
			}
			else
			{
				LOG_DEBUG_DATA("Found cache entry for request (" << getResource() << "): " << relativePath);

				if (cacheSetting == 0)
				{
					// cache is disabled
					// copy & re-use filePath and mime_type
					responseFile.setFilePath(cacheItr->second.getFilePath());
					responseFile.setMimeType(cacheItr->second.getMimeType());

					// get the file_size and last_modified timestamp
					responseFile.update();

					// just compare strings for simplicity (parsing this date format sucks!)
					if (responseFile.getLastModifiedString() == ifModifiedSince)
					{
						// no need to read the file; the modified times match!
						responseType = RESPONSE_NOT_MODIFIED;
					}
					else
					{
						if (request->getMethod() == pion::net::HTTPTypes::REQUEST_METHOD_HEAD)
						{
							responseType = RESPONSE_HEAD_OK;
						}
						else
						{
							responseType = RESPONSE_OK;
							LOG_TRACE_DATA("Cache disabled, reading file (" << getResource() << "): " << relativePath);
						}
					}
				}
				else
				{
					// cache is enabled
					// true if the entry was updated (used for log message)
					bool cacheWasUpdated = false;

					if (cacheItr->second.getLastModified() == 0)
					{
						// cache file for the first time
						cacheWasUpdated = true;
						cacheItr->second.update();
						if (maxCacheSize==0 || cacheItr->second.getFileSize() <= maxCacheSize)
							cacheItr->second.read();
						else
							cacheItr->second.resetFileContent();
					}
					else if (cacheSetting == 1)
					{
						cacheWasUpdated = cacheItr->second.checkUpdated();
					}

					// get the response type
					if (cacheItr->second.getLastModifiedString() == ifModifiedSince)
						responseType = RESPONSE_NOT_MODIFIED;
					else if (request->getMethod() == pion::net::HTTPTypes::REQUEST_METHOD_HEAD)
						responseType = RESPONSE_HEAD_OK;
					else
						responseType = RESPONSE_OK;

					// copy cache contents so that we can release the mutex
					responseFile = cacheItr->second;

					LOG_DEBUG_DATA((cacheWasUpdated ? "Updated" : "Using") << " cache entry for request (" << getResource() << "): " << relativePath);
				}
			}
		}

		if (responseType == RESPONSE_UNDEFINED)
		{
			// make sure that the file exists
			if (! boost::filesystem::exists(filePath))
			{
				LOG_WARN_DATA("File not found (" << getResource() << "): " << relativePath);
				sendNotFoundResponse(request, tcpConn);
				return;
			}

			responseFile.setFilePath(filePath);

			LOG_DEBUG_DATA("Found file for request (" << getResource() << "): " << relativePath);

			responseFile.setMimeType(rgcpp::MIMETypes::findMIMEType(responseFile.getFilePath().leaf()));
			responseFile.update();

			// just compare strings for simplicity (parsing this date format sucks!)
			if (responseFile.getLastModifiedString() == ifModifiedSince)
				responseType = RESPONSE_NOT_MODIFIED;
			else if (request->getMethod() == pion::net::HTTPTypes::REQUEST_METHOD_HEAD)
				responseType = RESPONSE_HEAD_OK;
			else
			{
				responseType = RESPONSE_OK;
				if (cacheSetting != 0)
				{
					if (maxCacheSize==0 || responseFile.getFileSize() <= maxCacheSize)
						responseFile.read();

					LOG_DEBUG_DATA("Adding cache entry for request (" << getResource() << "): " << relativePath);

					boost::mutex::scoped_lock cacheLock(cacheMutex);
					cacheMap.insert(std::make_pair(relativePath,responseFile));
				}
			}
		}

		if (responseType == RESPONSE_OK)
		{
			boost::shared_ptr<DiskFileSender> senderPtr(DiskFileSender::create(responseFile, request, tcpConn, maxChunkSize));
			senderPtr->send();
		}
		else if (responseType == RESPONSE_NOT_FOUND)
		{
			sendNotFoundResponse(request, tcpConn);
		}
		else
		{
			pion::net::HTTPResponseWriterPtr writer(pion::net::HTTPResponseWriter::create(tcpConn, *request,boost::bind(&pion::net::TCPConnection::finish, tcpConn)));
			writer->getResponse().setContentType(responseFile.getMimeType());

			// set Last-Modified header to enable client-side caching
			writer->getResponse().addHeader(pion::net::HTTPTypes::HEADER_LAST_MODIFIED, responseFile.getLastModifiedString());

			switch(responseType)
			{
				case RESPONSE_UNDEFINED:
				case RESPONSE_NOT_FOUND:
				case RESPONSE_OK:
				{
					Throw(rgcpp::IllegalStateException(request->getResource()));
				}break;
				case RESPONSE_NOT_MODIFIED:
				{
					writer->getResponse().setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_NOT_MODIFIED);
					writer->getResponse().setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_NOT_MODIFIED);
				}break;
				case RESPONSE_HEAD_OK:
				{
					writer->getResponse().setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_OK);
					writer->getResponse().setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_OK);
				}break;
			}

			writer->send();
		}
	}
	else if (request->getMethod() == pion::net::HTTPTypes::REQUEST_METHOD_POST || request->getMethod() == pion::net::HTTPTypes::REQUEST_METHOD_PUT || request->getMethod() == pion::net::HTTPTypes::REQUEST_METHOD_DELETE)
	{
		// If not writable, then send 405 (Method Not Allowed) response for POST, PUT or DELETE requests.
		if (!writable)
		{
			static const std::string NOT_ALLOWED_HTML_START =	"<html><head>\n"
																"<title>405 Method Not Allowed</title>\n"
																"</head><body>\n"
																"<h1>Not Allowed</h1>\n"
																"<p>The requested method ";

			static const std::string NOT_ALLOWED_HTML_FINISH =	" is not allowed on this server.</p>\n"
																"</body></html>\n";

			pion::net::HTTPResponseWriterPtr writer(pion::net::HTTPResponseWriter::create(tcpConn, *request, boost::bind(&pion::net::TCPConnection::finish, tcpConn)));

			writer->getResponse().setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_METHOD_NOT_ALLOWED);
			writer->getResponse().setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_METHOD_NOT_ALLOWED);
			writer->writeNoCopy(NOT_ALLOWED_HTML_START);
			writer << request->getMethod();
			writer->writeNoCopy(NOT_ALLOWED_HTML_FINISH);
			writer->getResponse().addHeader("Allow", "GET, HEAD");
			writer->send();
		}
		else
		{
			pion::net::HTTPResponseWriterPtr writer(pion::net::HTTPResponseWriter::create(tcpConn, *request, boost::bind(&pion::net::TCPConnection::finish, tcpConn)));
			if (request->getMethod() == pion::net::HTTPTypes::REQUEST_METHOD_POST || request->getMethod() == pion::net::HTTPTypes::REQUEST_METHOD_PUT)
			{
				if (boost::filesystem::exists(filePath))
				{
					writer->getResponse().setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_NO_CONTENT);
					writer->getResponse().setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_NO_CONTENT);
				}
				else
				{
					// directory of the requested file also doesn't exist.
					if (!boost::filesystem::exists(filePath.branch_path()))
					{
						static const std::string NOT_FOUND_HTML_START =	"<html><head>\n"
																		"<title>404 Not Found</title>\n"
																		"</head><body>\n"
																		"<h1>Not Found</h1>\n"
																		"<p>The directory of the requested URL ";

						static const std::string NOT_FOUND_HTML_FINISH =	" was not found on this server.</p>\n"
																			"</body></html>\n";

						writer->getResponse().setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_NOT_FOUND);
						writer->getResponse().setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_NOT_FOUND);
						writer->writeNoCopy(NOT_FOUND_HTML_START);
						writer << request->getResource();
						writer->writeNoCopy(NOT_FOUND_HTML_FINISH);
						writer->send();
						return;
					}

					static const std::string CREATED_HTML_START =	"<html><head>\n"
																	"<title>201 Created</title>\n"
																	"</head><body>\n"
																	"<h1>Created</h1>\n"
																	"<p>";

					static const std::string CREATED_HTML_FINISH =	"</p>\n"
																	"</body></html>\n";

					writer->getResponse().setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_CREATED);
					writer->getResponse().setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_CREATED);
					writer->getResponse().addHeader(pion::net::HTTPTypes::HEADER_LOCATION, request->getResource());
					writer->writeNoCopy(CREATED_HTML_START);
					writer << request->getResource();
					writer->writeNoCopy(CREATED_HTML_FINISH);
				}

				std::ios_base::openmode mode = request->getMethod() == pion::net::HTTPTypes::REQUEST_METHOD_POST ? std::ios::app : std::ios::out;
				boost::filesystem::ofstream fileStream(filePath, mode);
				fileStream.write(request->getContent(), request->getContentLength());
				fileStream.close();

				if (!boost::filesystem::exists(filePath))
				{
					static const std::string PUT_FAILED_HTML_START =	"<html><head>\n"
																		"<title>500 Server Error</title>\n"
																		"</head><body>\n"
																		"<h1>Server Error</h1>\n"
																		"<p>Error writing to ";

					static const std::string PUT_FAILED_HTML_FINISH =	".</p>\n"
																		"</body></html>\n";

					writer->getResponse().setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_SERVER_ERROR);
					writer->getResponse().setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_SERVER_ERROR);
					writer->writeNoCopy(PUT_FAILED_HTML_START);
					writer << request->getResource();
					writer->writeNoCopy(PUT_FAILED_HTML_FINISH);
				}

				writer->send();
			}
			else if (request->getMethod() == pion::net::HTTPTypes::REQUEST_METHOD_DELETE)
			{
				if (!boost::filesystem::exists(filePath))
				{
					sendNotFoundResponse(request, tcpConn);
				}
				else
				{
					try
					{
						boost::filesystem::remove(filePath);
						writer->getResponse().setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_NO_CONTENT);
						writer->getResponse().setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_NO_CONTENT);
						writer->send();
					}
					catch (...)
					{
						static const std::string DELETE_FAILED_HTML_START =	"<html><head>\n"
																			"<title>500 Server Error</title>\n"
																			"</head><body>\n"
																			"<h1>Server Error</h1>\n"
																			"<p>Could not delete ";

						static const std::string DELETE_FAILED_HTML_FINISH =	".</p>\n"
																				"</body></html>\n";

						writer->getResponse().setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_SERVER_ERROR);
						writer->getResponse().setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_SERVER_ERROR);
						writer->writeNoCopy(DELETE_FAILED_HTML_START);
						writer << request->getResource();
						writer->writeNoCopy(DELETE_FAILED_HTML_FINISH);
						writer->send();
					}
				}
			}
			else
			{
				writer->getResponse().setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_SERVER_ERROR);
				writer->getResponse().setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_SERVER_ERROR);
				writer->send();
			}
		}
	}
	else // Any method not handled above is unimplemented.
	{
		static const std::string NOT_IMPLEMENTED_HTML_START =	"<html><head>\n"
																"<title>501 Not Implemented</title>\n"
																"</head><body>\n"
																"<h1>Not Implemented</h1>\n"
																"<p>The requested method ";

		static const std::string NOT_IMPLEMENTED_HTML_FINISH =	" is not implemented on this server.</p>\n"
																"</body></html>\n";

		pion::net::HTTPResponseWriterPtr writer(pion::net::HTTPResponseWriter::create(tcpConn, *request,boost::bind(&pion::net::TCPConnection::finish, tcpConn)));

		writer->getResponse().setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_NOT_IMPLEMENTED);
		writer->getResponse().setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_NOT_IMPLEMENTED);
		writer->writeNoCopy(NOT_IMPLEMENTED_HTML_START);
		writer << request->getMethod();
		writer->writeNoCopy(NOT_IMPLEMENTED_HTML_FINISH);
		writer->send();
	}
}

void HttpFileService::start()
{
	LOG_DEBUG_DATA("Starting up resource (" << getResource() << ')');

	if (scanSetting != 0)
	{
		if (cacheSetting == 0 && scanSetting > 1)
			cacheSetting = 1;

		boost::mutex::scoped_lock cacheLock(cacheMutex);

		if (!file.empty())
			this->addCacheEntry("", file, scanSetting == 1);

		if (! directory.empty())
			this->scanDirectory(directory);
	}
}

void HttpFileService::stop()
{
	LOG_DEBUG_DATA("Shutting down resource (" << getResource() << ')');

	boost::mutex::scoped_lock cacheLock(cacheMutex);
	cacheMap.clear();
}

void HttpFileService::scanDirectory(const boost::filesystem::path& dirPath)
{
	LOG_DEBUG_DATA("Scanning directory (" << getResource() << "): " << dirPath.directory_string());

	boost::filesystem::directory_iterator endItr;
	for ( boost::filesystem::directory_iterator itr(dirPath); itr != endItr; ++itr )
	{
		if ( boost::filesystem::is_directory(*itr) )
		{
			scanDirectory(*itr);

		}
		else
		{
			std::string filePath( itr->path().file_string() );
			std::string relativePath( filePath.substr(directory.directory_string().size() + 1) );

			this->addCacheEntry(relativePath, *itr, scanSetting == 1);
		}
	}
}

std::pair<rgcpp::Map<std::string,DiskFile>::iterator, bool>  HttpFileService::addCacheEntry(const std::string& relativePath, const boost::filesystem::path& filePath, const bool placeholder)
{

	DiskFile cacheEntry(filePath, NULL, 0, 0, rgcpp::MIMETypes::findMIMEType(filePath.leaf()));
	if (! placeholder)
	{
		cacheEntry.update();
		if (maxCacheSize==0 || cacheEntry.getFileSize() <= maxCacheSize)
		{
			try
			{
				cacheEntry.read();
			}
			catch (std::exception&)
			{
				LOG_ERROR_DATA("Unable to add file to cache: " << filePath.file_string());

				return std::make_pair(cacheMap.end(), false);
			}
		}
	}

	std::pair<rgcpp::Map<std::string,DiskFile>::iterator, bool> result = cacheMap.insert(std::make_pair(relativePath,cacheEntry));

	if (result.second)
	{
		LOG_TRACE_DATA("Added file to cache: " << filePath.file_string());
	}
	else
	{
		LOG_ERROR_DATA("Unable to insert cache entry for file: " << filePath.file_string());
	}

	return result;
}

void HttpFileService::sendNotFoundResponse(pion::net::HTTPRequestPtr& httpRequest, pion::net::TCPConnectionPtr& tcpConn)
{
	static const std::string NOT_FOUND_HTML_START = "<html><head>\n"
													"<title>404 Not Found</title>\n"
													"</head><body>\n"
													"<h1>Not Found</h1>\n"
													"<p>The requested URL ";

	static const std::string NOT_FOUND_HTML_FINISH = " was not found on this server.</p>\n"
													 "</body></html>\n";

	pion::net::HTTPResponseWriterPtr writer(pion::net::HTTPResponseWriter::create(tcpConn, *httpRequest, boost::bind(&pion::net::TCPConnection::finish, tcpConn)));

	writer->getResponse().setStatusCode(pion::net::HTTPTypes::RESPONSE_CODE_NOT_FOUND);
	writer->getResponse().setStatusMessage(pion::net::HTTPTypes::RESPONSE_MESSAGE_NOT_FOUND);

	if (httpRequest->getMethod() != pion::net::HTTPTypes::REQUEST_METHOD_HEAD)
	{
		writer->writeNoCopy(NOT_FOUND_HTML_START);
		writer << httpRequest->getResource();
		writer->writeNoCopy(NOT_FOUND_HTML_FINISH);
	}

	writer->send();
}

}

extern "C" PION_SERVICE_API acs::HttpFileService* pion_create_HttpFileService()
{
	return new acs::HttpFileService();
}

extern "C" PION_SERVICE_API void pion_destroy_HttpFileService(acs::HttpFileService* servicePtr)
{
	delete servicePtr;
}
